/**
 * Copyright (c) 2011 Tomi Paananen.
 *
 * Part of Quick Gestures test tool.
 */

// Own header
#include "qgtesttool.h"

// Qt includes
#include <QCoreApplication>
#include <QDebug>
#include <QTimer>

// qgengine includes
#include <classifierthread.h>
#include <featureextractor.h>
#include <gesture.h>
#include <gesturemodel.h>
#include <qgcommon.h>
#include <qgutils.h>
#include <quickgesturesengine.h>
#include <simpleclassifier.h>
#include <storagemanager.h>

// qgtesttoolincludes
#include "qgtesttoolcommon.h"
#include "setexaminer.h"
#include "statistics.h"

// Constants
const char HelpText[] =
    "\n\t<sample set>\n\t<folder path containing test sets>"
    "\n\t-c<min. point count> <max. point count>"
    "\n\t-s<min. scale factor> <max. scale factor>"
    "\n"
    "\n\t-examine <a single gesture set file>/<path to sets>"
    "\n";
const QString GestureSetNameFilter("*.xml");
const QString PointCountArg("-c");
const QString scaleFactorArg("-s");
const int UndefinedValue(-1);
const int PointCountIncrement(10);
const int ScaleFactorIncrement(50);
const int DefaultPointCount(40);
const quint32 DefaultScaleFactor(200);

using namespace QGEngine;


/*!
  \class QGTestTool
  \brief The main class of the test tool application.
*/


/*!
  Constructor.
*/
QGTestTool::QGTestTool(int argc, char *argv[], QObject *parent)
    : QObject(parent),
      m_classifier(0),
      m_featureExtractor(0),
      m_storageManager(0),
      m_statistics(new Statistics()),
      m_sampleSetFilename(QString()),
      m_examine(false)
{
    m_pointCounts[0] = UndefinedValue;
    m_scaleFactors[0] = UndefinedValue;

    PRINT << "Quick Gestures test tool"
          << "\nVisit http://projects.developer.nokia.com/quickgestures\n";

    if (!processArguments(argc, argv)) {
        // Failed to process the arguments!
        printHelpText(argv[0]);
        QTimer::singleShot(1000, this, SLOT(quit()));
    }
    else {
        m_storageManager = new StorageManager(this);

        if (m_examine) {
            examine();
        }
        else {
            startTest();
        }
    }

    QTimer::singleShot(1000, this, SLOT(quit()));
}


/*!
  Destructor.
*/
QGTestTool::~QGTestTool()
{
    PRINT << "Exiting test tool...";
    delete m_classifier;
    delete m_statistics;
}


/*!
  Processes the command line arguments.
*/
bool QGTestTool::processArguments(int argc, char *argv[])
{
    if (argc < 3) {
        // Too few arguments!
        qDebug() << "Error: Too few arguments!";
        return false;
    }

    // Check if we're about to examine gesture set(s).
    if (!QString(argv[1]).compare("-examine")) {
        m_examine = true;

        if (QString(argv[2]).endsWith(".xml")) {
            m_sampleSetFilename = QString(argv[2]);
            QFile sampleSetFile(m_sampleSetFilename);

            if (!sampleSetFile.exists()) {
                qDebug() << "Error: Failed to find" << m_sampleSetFilename << "!";
                return false;
            }
        }
        else {
            m_testSetPath = QString(argv[2]);
            m_testSetEntries = QDir(m_testSetPath, GestureSetNameFilter);

            if (!m_testSetEntries.entryList().count()
                    || (m_testSetEntries.count() == 1 &&
                        !m_testSetEntries.entryList().at(0).compare(m_sampleSetFilename))) {
                qDebug() << "Error: No gesture sets in path" << m_testSetPath << "!";
                return false;
            }
        }

        return true;
    }

    m_sampleSetFilename = QString(argv[1]);
    QFile sampleSetFile(m_sampleSetFilename);

    if (!sampleSetFile.exists()) {
        qDebug() << "Error: Failed to find" << m_sampleSetFilename << "!";
        return false;
    }

    m_testSetPath = QString(argv[2]);
    m_testSetEntries = QDir(m_testSetPath, GestureSetNameFilter);

    if (!m_testSetEntries.entryList().count()
            || (m_testSetEntries.count() == 1 &&
                !m_testSetEntries.entryList().at(0).compare(m_sampleSetFilename))) {
        qDebug() << "Error: No test sets in path" << m_testSetPath << "!";
        return false;
    }

    QString arg;

    for (int i = 3; i < argc; ++i) {
        //qDebug() << "Processing argument" << i << ":" << argv[i];

        arg = QString(argv[i]);

        if (arg.startsWith(PointCountArg)) {
            QString temp = arg.right(PointCountArg.length());
            bool ok1(false);
            int value1(temp.toInt(&ok1));

            if (argc - 1 > i) {
                bool ok2(false);
                QString secondArg(argv[i + 1]);
                int value2(secondArg.toInt(&ok2));

                if (ok1 && ok2 && value1 < value2 && value1 > 1 && value2 > 2) {
                    qDebug() << "Point counts: [" << value1 << "," << value2 << "]";
                    m_pointCounts[0] = value1;
                    m_pointCounts[1] = value2;
                    i += 1;
                    continue;
                }
                else {
                    qDebug() << "Error: Invalid point count arguments:"
                             << value1 << "," << value2 << "!";
                    return false;
                }
            }
            else {
                qDebug() << "Error: Missing second point count value!";
                return false;
            }
        }
        else if (arg.startsWith(scaleFactorArg)) {
            QString temp = arg.right(scaleFactorArg.length());
            bool ok1(false);
            quint32 value1(temp.toFloat(&ok1));

            if (argc - 1 > i) {
                bool ok2(false);
                QString secondArg(argv[i + 1]);
                quint32 value2(secondArg.toFloat(&ok2));

                if (ok1 && ok2 && value1 < value2 && value1 > 1 && value2 > 2) {
                    qDebug() << "Scale ratios: [" << value1 << "," << value2 << "]";
                    m_scaleFactors[0] = value1;
                    m_scaleFactors[1] = value2;
                    i += 1;
                    continue;
                }
                else {
                    qDebug() << "Error: Invalid scale ratio arguments:"
                             << value1 << "," << value2 << "!";
                    return false;
                }
            }
            else {
                qDebug() << "Error: Missing second scale ratio value!";
                return false;
            }
        }
        else {
            qDebug() << "Error: Unrecognized argument:" << argv[i];
            return false;
        }
    }

    if (m_pointCounts[0] == UndefinedValue) {
        qDebug() << "Warning: Using default point count:" << DefaultPointCount;
        m_pointCounts[0] = DefaultPointCount;
        m_pointCounts[1] = m_pointCounts[0];
    }

    if (m_scaleFactors[0] == (quint32)UndefinedValue) {
        qDebug() << "Warning: Using default scale factor:" << DefaultScaleFactor;
        m_scaleFactors[0] = DefaultScaleFactor;
        m_scaleFactors[1] = m_scaleFactors[0];
    }

    return true;
}


/*!
  Prints the help text.
*/
void QGTestTool::printHelpText(char *arg0) const
{
    PRINT << "\nUsage:" << arg0 << HelpText;
}


/*!
*/
void QGTestTool::startTest()
{
    PRINT << "Loading sample set...";

    if (m_storageManager->load(m_sampleSetFilename) != NoError) {
        PRINT << "Error: Failed to load samples from" << m_sampleSetFilename;
        return;
    }

    QList<Gesture*> sampleSet;
    m_storageManager->getGestures(sampleSet);

    PRINT << "Loaded" << sampleSet.count() << "gestures from"
          << m_sampleSetFilename;

    m_classifier = new SimpleClassifier();
    m_classifier->setRecognitionThreshold(MaxInt);

    m_featureExtractor = new FeatureExtractor(this);

    QList<Gesture*> processedSampleSet;
    QList<Gesture*> testSet;

    for (int pointCount = m_pointCounts[0];
         pointCount <= m_pointCounts[1];
         pointCount += PointCountIncrement)
    {
        for (quint32 scaleFactor = m_scaleFactors[0];
             scaleFactor <= m_scaleFactors[1];)
        {
            for (unsigned int i = 0; i < m_testSetEntries.count(); ++i) {
                QString path(m_testSetPath);

                if (!path.endsWith('/') && !path.endsWith('\\')) {
                    path += '/';
                }

                path += m_testSetEntries[i];

                // Load the test set.
                if (loadGestureSet(path, testSet) != NoError) {
                    qDebug() << "Failed to load test set from" << path;
                    continue;
                }
                else {
                    PRINT << "Loaded" << testSet.count() << "gestures from"
                          << path;
                }

                // Copy the sample set gestures.
                QGUtils::copyGestureSet(sampleSet, processedSampleSet);

                // Set parameters and process.
                setParameters(pointCount, scaleFactor);
                processGestureSet(processedSampleSet);
                processGestureSet(testSet);

                // Run the test.
                runTest(processedSampleSet, testSet, m_testSetEntries[i]);

                // Delete the processed sample set gestures.
                PRINT << "Deleting processed sample set gestures...";
                qDeleteAll(processedSampleSet);
                processedSampleSet.clear();

                // Delete the test set.
                //PRINT << "Deleting the test set...";
                qDeleteAll(testSet);
                testSet.clear();
            } // testSetEntries

            PRINT << "Exporting statistics...";
            QString metaData("Parameters:<br/>\n- point count: ");
            metaData += QString::number(pointCount) + "<br/>\n- scale factor: "
                    + QString::number(scaleFactor) + "\n";
            m_statistics->setMetaData(metaData);
            QString resultsPath("./results-");
            resultsPath += QString::number(pointCount) + "-"
                    + QString::number(scaleFactor) + ".html";
            m_statistics->exportAsHTML(resultsPath);
            m_statistics->clear();

            // Check the scale factor: Increase or exit the loop if reached
            // the maximum.
            if (scaleFactor >= m_scaleFactors[1]) {
                // Exit.
                break;
            }
            else if (scaleFactor + ScaleFactorIncrement > m_scaleFactors[1]) {
                scaleFactor = m_scaleFactors[1];
            }
            else {
                scaleFactor += ScaleFactorIncrement;
            }
        } // scaleFactor
    } // pointCount

    PRINT << "Test completed!";
    PRINT << "Deleting the unprocessed sample set...";
    qDeleteAll(sampleSet);
}


/*!
*/
void QGTestTool::examine()
{
    qDebug() << "QGTestTool::examine()";
    SetExaminer setExaminer;
    QList<Gesture*> gestureSet;
    QString filePath("./examination.html");

    if (m_sampleSetFilename.isEmpty()) {
        // Examine a folder of sets.
        PRINT << "Starting examination of sets in" << m_testSetPath;

        for (unsigned int i = 0; i < m_testSetEntries.count(); ++i) {
            QString path(m_testSetPath);

            if (!path.endsWith('/') && !path.endsWith('\\')) {
                path += '/';
            }

            path += m_testSetEntries[i];

            // Load the gesture set.
            if (loadGestureSet(path, gestureSet) != NoError) {
                qDebug() << "Failed to load test set from" << path;
                continue;
            }
            else {
                PRINT << "Loaded" << gestureSet.count() << "gestures from"
                      << path;
            }

            bool append((i > 0));
            setExaminer.exportAsHTML(filePath, gestureSet, path, append);

            // Delete the gesture set.
            qDeleteAll(gestureSet);
            gestureSet.clear();
        }

        setExaminer.exportTotalFiguresAsHTML(filePath);
    }
    else {
        // Examine a single gesture set.
        // Load the gesture set.
        if (loadGestureSet(m_sampleSetFilename, gestureSet) != NoError) {
            qDebug() << "Failed to load test set from" << m_sampleSetFilename;
        }
        else {
            setExaminer.exportAsHTML(filePath, gestureSet, m_sampleSetFilename, false);
            setExaminer.exportTotalFiguresAsHTML(filePath);

            // Delete the gesture set.
            qDeleteAll(gestureSet);
            gestureSet.clear();
        }
    }

    PRINT << "Examination completed!";
}


/*!
  Loads a gesture set from \a path and inserts the loaded set into
  \a gestureSet. Returns QGEngine::NoError if successful, an error code
  otherwise.
*/
int QGTestTool::loadGestureSet(const QString &path,
                               QList<Gesture*> &gestureSet)
{
    int error = m_storageManager->load(path);

    if (error != NoError) {
        return error;
    }

    m_storageManager->getGestures(gestureSet);
    return NoError;
}


/*!
  Sets the desired point count and scale factor value.
*/
void QGTestTool::setParameters(int pointCount, quint32 scaleFactor)
{
    m_featureExtractor->setGesturePointCount(pointCount);
    m_featureExtractor->setGestureScaleFactor(scaleFactor);
}


/*!
  Processes \a gestureSet.
*/
void QGTestTool::processGestureSet(QList<Gesture*> &gestureSet)
{
    m_featureExtractor->processGestureSet(gestureSet);
}


/*!
*/
void QGTestTool::runTest(QList<Gesture*> &sampleSet, QList<Gesture*> &testSet,
                         const QString &testSetId)
{
    if (sampleSet.count() != testSet.count()) {
        PRINT << "QGTestTool::runTest(): WARNING:"
              << "Test set count does not match sample set count!";
    }

    const int count(sampleSet.count() <= testSet.count() ?
                        sampleSet.count() : testSet.count());
    int diff(0);
    int minDiff(0);
    int bestIndex(0);

    for (int i = 0; i < count; ++i) {
        for (int j = 0; j < count; ++j) {
            diff = m_classifier->compare(sampleSet[i], testSet[j]);

            if (j == 0) {
                minDiff = diff;
                bestIndex = 0;
            }
            else {
                if (diff < minDiff) {
                    minDiff = diff;
                    bestIndex = j;
                }
            }
        }

        // Add the result to the statistics.
        m_statistics->addValue(testSetId, i, bestIndex, minDiff);
        PRINT << "Adding values to the table:"
              << testSetId << i << bestIndex << minDiff;
    }
}


/*!
  Exits the application.
*/
void QGTestTool::quit()
{
    QCoreApplication::instance()->quit();
}


// End of file.
